#!/usr/bin/python3 -i
#
# Copyright (c) 2015-2025 The Khronos Group Inc.
# Copyright (c) 2015-2025 Valve Corporation
# Copyright (c) 2015-2025 LunarG, Inc.
# Copyright (c) 2015-2025 Google Inc.
# Copyright (c) 2023-2025 RasterGrid Kft.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
from generators.generator_utils import buildListVUID, getVUID, PlatformGuardHelper
from vulkan_object import Handle, Command, Struct, Member, Param
from base_generator import BaseGenerator

# This class is a container for any source code, data, or other behavior that is necessary to
# customize the generator script for a specific target API variant (e.g. Vulkan SC). As such,
# all of these API-specific interfaces and their use in the generator script are part of the
# contract between this repository and its downstream users. Changing or removing any of these
# interfaces or their use in the generator script will have downstream effects and thus
# should be avoided unless absolutely necessary.
class APISpecific:
    # Tells whether an object handle type is implicitly destroyed because it does not have
    # destroy APIs or its parent object type does not have destroy APIs
    @staticmethod
    def IsImplicitlyDestroyed(targetApiName: str, handleType: str) -> bool:
        match targetApiName:

            # Vulkan specific implicitly destroyed handle types
            case 'vulkan':
                implicitly_destroyed_set = {
                'VkDisplayKHR',
                'VkDisplayModeKHR'
                }

        return handleType in implicitly_destroyed_set


    # Returns whether allocation callback related VUIDs are enabled
    @staticmethod
    def AreAllocVUIDsEnabled(targetApiName: str) -> bool:
        match targetApiName:

            # Vulkan has allocation callback related VUIDs
            case 'vulkan':
                return True


class ObjectTrackerOutputGenerator(BaseGenerator):
    def __init__(self,
                 valid_usage_file):
        BaseGenerator.__init__(self)
        self.valid_vuids = buildListVUID(valid_usage_file)

        # Commands which are not autogenerated but still intercepted
        self.no_autogen_list = [
            'vkCreateInstance',
            'vkDestroyInstance',
            'vkCreateDevice',
            'vkDestroyDevice',
            # These don't have object tracing but will fail in loader if not here
            'vkGetPhysicalDeviceQueueFamilyProperties',
            'vkGetPhysicalDeviceQueueFamilyProperties2',
            'vkGetPhysicalDeviceQueueFamilyProperties2KHR',
            # Need to manually track commmand buffer life times
            'vkDestroyCommandPool',
            'vkAllocateCommandBuffers',
            'vkFreeCommandBuffers',
            'vkBeginCommandBuffer',
            # issues with VkDeferredOperationKHR
            'vkCreateRayTracingPipelinesKHR',
            # Descriptor management is done by hand
            'vkDestroyDescriptorPool',
            'vkAllocateDescriptorSets',
            'vkFreeDescriptorSets',
            'vkUpdateDescriptorSets',
            'vkCmdPushDescriptorSet',
            'vkCmdPushDescriptorSetKHR',
            'vkCmdPushDescriptorSet2',
            'vkCmdPushDescriptorSet2KHR',
            'vkResetDescriptorPool',
            'vkCreateDescriptorUpdateTemplate',
            'vkCreateDescriptorUpdateTemplateKHR',
        ]

        self.post_call_record_additional_parameters = {
            "vkCreateRayTracingPipelinesKHR": "PipelineStates &pipeline_states, std::shared_ptr<chassis::CreateRayTracingPipelinesKHR> chassis_state"
        }

        # Commands which source are not autogenerated for PreCallValidate
        self.no_validate_autogen_list = [
            'vkExportMetalObjectsEXT',
            # Need to validate the handles here manually
            'vkDebugMarkerSetObjectNameEXT',
            'vkDebugMarkerSetObjectTagEXT',
            'vkSetDebugUtilsObjectNameEXT',
            'vkSetDebugUtilsObjectTagEXT',
            'vkGetPrivateData',
            'vkSetPrivateData',
            'vkReleaseCapturedPipelineDataKHR',
            # Need to check the VkDescriptorType
            'vkCreateDescriptorSetLayout',
            'vkGetDescriptorSetLayoutSupport',
            # Need to check the flag and attachmentCount
            'vkCreateFramebuffer',
            # Can't handle VkAccelerationStructureBuildGeometryInfoKHR in code gen currently
            'vkBuildAccelerationStructuresKHR',
            'vkCmdBuildAccelerationStructuresKHR',
            'vkCmdBuildAccelerationStructuresIndirectKHR',
            # Union of pointers
            'vkGetDescriptorEXT',
            'vkCreateIndirectExecutionSetEXT',
        ]

        # Commands which source are not autogenerated for PreCallRecord
        self.no_pre_record_autogen_list = [
            'vkDestroySwapchainKHR',
            # For tracking lifetime of linked GPL libraries
            'vkDestroyPipeline',
        ]

        # Commands which source are not autogenerated for PostCallRecord
        self.no_post_record_autogen_list = [
            # VkQueue are not created and instead acquired, so track differently
            'vkGetDeviceQueue',
            'vkGetDeviceQueue2',
            # Need to get and allocate the VkDisplayKHR
            'vkGetPhysicalDeviceDisplayPropertiesKHR',
            'vkGetPhysicalDeviceDisplayProperties2KHR',
            'vkGetDisplayModePropertiesKHR',
            'vkGetDisplayModeProperties2KHR',
            # Swapchain images handles are acquired
            'vkGetSwapchainImagesKHR',
            # For tracking lifetime of linked GPL libraries
            'vkCreateGraphicsPipelines',
            'vkCreatePipelineBinariesKHR',
        ]

        # These VUIDS are not implicit, but are best handled in this layer. Codegen for vkDestroy calls will generate a key
        # which is translated here into a good VU.  Saves ~40 checks.
        self.manual_vuids = dict()
        self.manual_vuids = {
            "fence-compatalloc": '"VUID-vkDestroyFence-fence-01121"',
            "fence-nullalloc": '"VUID-vkDestroyFence-fence-01122"',
            "event-compatalloc": '"VUID-vkDestroyEvent-event-01146"',
            "event-nullalloc": '"VUID-vkDestroyEvent-event-01147"',
            "buffer-compatalloc": '"VUID-vkDestroyBuffer-buffer-00923"',
            "buffer-nullalloc": '"VUID-vkDestroyBuffer-buffer-00924"',
            "image-compatalloc": '"VUID-vkDestroyImage-image-01001"',
            "image-nullalloc": '"VUID-vkDestroyImage-image-01002"',
            "shaderModule-compatalloc": '"VUID-vkDestroyShaderModule-shaderModule-01092"',
            "shaderModule-nullalloc": '"VUID-vkDestroyShaderModule-shaderModule-01093"',
            "pipeline-compatalloc": '"VUID-vkDestroyPipeline-pipeline-00766"',
            "pipeline-nullalloc": '"VUID-vkDestroyPipeline-pipeline-00767"',
            "sampler-compatalloc": '"VUID-vkDestroySampler-sampler-01083"',
            "sampler-nullalloc": '"VUID-vkDestroySampler-sampler-01084"',
            "renderPass-compatalloc": '"VUID-vkDestroyRenderPass-renderPass-00874"',
            "renderPass-nullalloc": '"VUID-vkDestroyRenderPass-renderPass-00875"',
            "descriptorUpdateTemplate-compatalloc": '"VUID-vkDestroyDescriptorUpdateTemplate-descriptorSetLayout-00356"',
            "descriptorUpdateTemplate-nullalloc": '"VUID-vkDestroyDescriptorUpdateTemplate-descriptorSetLayout-00357"',
            "imageView-compatalloc": '"VUID-vkDestroyImageView-imageView-01027"',
            "imageView-nullalloc": '"VUID-vkDestroyImageView-imageView-01028"',
            "pipelineCache-compatalloc": '"VUID-vkDestroyPipelineCache-pipelineCache-00771"',
            "pipelineCache-nullalloc": '"VUID-vkDestroyPipelineCache-pipelineCache-00772"',
            "pipelineLayout-compatalloc": '"VUID-vkDestroyPipelineLayout-pipelineLayout-00299"',
            "pipelineLayout-nullalloc": '"VUID-vkDestroyPipelineLayout-pipelineLayout-00300"',
            "descriptorSetLayout-compatalloc": '"VUID-vkDestroyDescriptorSetLayout-descriptorSetLayout-00284"',
            "descriptorSetLayout-nullalloc": '"VUID-vkDestroyDescriptorSetLayout-descriptorSetLayout-00285"',
            "semaphore-compatalloc": '"VUID-vkDestroySemaphore-semaphore-01138"',
            "semaphore-nullalloc": '"VUID-vkDestroySemaphore-semaphore-01139"',
            "queryPool-compatalloc": '"VUID-vkDestroyQueryPool-queryPool-00794"',
            "queryPool-nullalloc": '"VUID-vkDestroyQueryPool-queryPool-00795"',
            "bufferView-compatalloc": '"VUID-vkDestroyBufferView-bufferView-00937"',
            "bufferView-nullalloc": '"VUID-vkDestroyBufferView-bufferView-00938"',
            "surface-compatalloc": '"VUID-vkDestroySurfaceKHR-surface-01267"',
            "surface-nullalloc": '"VUID-vkDestroySurfaceKHR-surface-01268"',
            "framebuffer-compatalloc": '"VUID-vkDestroyFramebuffer-framebuffer-00893"',
            "framebuffer-nullalloc": '"VUID-vkDestroyFramebuffer-framebuffer-00894"',
            "VkGraphicsPipelineCreateInfo-basePipelineHandle": '"VUID-VkGraphicsPipelineCreateInfo-flags-07984"',
            "VkComputePipelineCreateInfo-basePipelineHandle": '"VUID-VkComputePipelineCreateInfo-flags-07984"',
            "VkRayTracingPipelineCreateInfoNV-basePipelineHandle": '"VUID-VkRayTracingPipelineCreateInfoNV-flags-07984"',
            "VkRayTracingPipelineCreateInfoKHR-basePipelineHandle": '"VUID-VkRayTracingPipelineCreateInfoKHR-flags-07984"',
            "VkVideoSessionKHR-videoSession-compatalloc": '"VUID-vkDestroyVideoSessionKHR-videoSession-07193"',
            "VkVideoSessionKHR-videoSession-nullalloc": '"VUID-vkDestroyVideoSessionKHR-videoSession-07194"',
            "VkVideoSessionParametersKHR-videoSessionParameters-compatalloc": '"VUID-vkDestroyVideoSessionParametersKHR-videoSessionParameters-07213"',
            "VkVideoSessionParametersKHR-videoSessionParameters-nullalloc": '"VUID-vkDestroyVideoSessionParametersKHR-videoSessionParameters-07214"',
            "VkAccelerationStructureKHR-accelerationStructure-compatalloc": '"VUID-vkDestroyAccelerationStructureKHR-accelerationStructure-02443"',
            "VkAccelerationStructureKHR-accelerationStructure-nullalloc": '"VUID-vkDestroyAccelerationStructureKHR-accelerationStructure-02444"',
            "VkAccelerationStructureNV-accelerationStructure-compatalloc": '"VUID-vkDestroyAccelerationStructureNV-accelerationStructure-03753"',
            "VkAccelerationStructureNV-accelerationStructure-nullalloc": '"VUID-vkDestroyAccelerationStructureNV-accelerationStructure-03754"',
            "shader-compatalloc": '"VUID-vkDestroyShaderEXT-pAllocator-08483"',
            "shader-nullalloc": '"VUID-vkDestroyShaderEXT-pAllocator-08484"',
            "callback-compatalloc": '"VUID-vkDestroyDebugReportCallbackEXT-instance-01242"',
            "callback-nullalloc": '"VUID-vkDestroyDebugReportCallbackEXT-instance-01243"',
            "messenger-compatalloc": '"VUID-vkDestroyDebugUtilsMessengerEXT-messenger-01915"',
            "messenger-nullalloc": '"VUID-vkDestroyDebugUtilsMessengerEXT-messenger-01916"',
            "operation-compatalloc": '"VUID-vkDestroyDeferredOperationKHR-operation-03434"',
            "operation-nullalloc": '"VUID-vkDestroyDeferredOperationKHR-operation-03435"',
            "micromap-compatalloc": '"VUID-vkDestroyMicromapEXT-micromap-07442"',
            "micromap-nullalloc": '"VUID-vkDestroyMicromapEXT-micromap-07443"',
            "privateDataSlot-compatalloc": '"VUID-vkDestroyPrivateDataSlot-privateDataSlot-04062"',
            "privateDataSlot-nullalloc": '"VUID-vkDestroyPrivateDataSlot-privateDataSlot-04063"',
            "validationCache-compatalloc": '"VUID-vkDestroyValidationCacheEXT-validationCache-01537"',
            "validationCache-nullalloc": '"VUID-vkDestroyValidationCacheEXT-validationCache-01538"',
            "swapchain-compatalloc": '"VUID-vkDestroySwapchainKHR-swapchain-01283"',
            "swapchain-nullalloc": '"VUID-vkDestroySwapchainKHR-swapchain-01284"',
            "pipelineBinary-compatalloc": '"VUID-vkDestroyPipelineBinaryKHR-pipelineBinary-09614"',
            "pipelineBinary-nullalloc": '"VUID-vkDestroyPipelineBinaryKHR-pipelineBinary-09615"',
            "VkIndirectCommandsLayoutEXT-indirectCommandsLayout-compatalloc": '"VUID-vkDestroyIndirectCommandsLayoutEXT-indirectCommandsLayout-11115"',
            "VkIndirectCommandsLayoutEXT-indirectCommandsLayout-nullalloc": '"VUID-vkDestroyIndirectCommandsLayoutEXT-indirectCommandsLayout-11116"',
           }

        # Structures that do not define parent/commonparent VUIDs for vulkan handles.
        # This overlaps with https://gitlab.khronos.org/vulkan/vulkan/-/issues/3553#note_424431
        self.structs_that_forgot_about_parent_vuids = [
            'VkPhysicalDeviceSurfaceInfo2KHR',
            'VkMicromapBuildInfoEXT',

            # Handled in manual check
            'VkDescriptorSetLayoutBinding',
            ]

        # Commands that define parent requirements using "-parent" instead of "-commonparent" VUID
        # for the cases when multiple objects share the same parent.
        self.use_parent_instead_of_commonparent_commands = [
            'vkBindBufferMemory',
            'vkBindImageMemory',
            'vkMergePipelineCaches',
            'vkCreateGraphicsPipelines',
            'vkCreateComputePipelines',
            'vkUpdateDescriptorSetWithTemplate',
            'vkUpdateDescriptorSetWithTemplateKHR',
            'vkAcquireNextImageKHR',
            'vkMergeValidationCachesEXT',
            'vkBindOpticalFlowSessionImageNV',
            ]

        # Commands that include the dispatchable parameter in the "-commonparent" list of handles.
        # For example, if the dispatchable parameter is VkDevice, but commonparent VUID defines
        # VkInstance as a parent then such VUID will also ask to validate VkDevice againt VkInstance.
        # In other cases, the dispatchable parameter is considered to be a parent and it is not
        # included in the VUID's list of handles.
        self.dispatchable_has_parent_vuid_commands = [
            'vkGetDeviceGroupSurfacePresentModesKHR',
            'vkDisplayPowerControlEXT',
            'vkRegisterDisplayEventEXT',
            'vkGetPhysicalDeviceSurfaceSupportKHR',
            'vkGetPhysicalDeviceSurfaceCapabilitiesKHR',
            'vkGetPhysicalDeviceSurfaceFormatsKHR',
            'vkGetPhysicalDeviceSurfacePresentModesKHR',
            'vkGetPhysicalDevicePresentRectanglesKHR',
            'vkGetPhysicalDeviceSurfaceCapabilities2EXT'
        ]

    # Work up Handle's parents to see if it VkDevice
    def isParentDevice(self, handle: Handle) -> bool:
        while handle.parent is not None:
            if handle.parent.name == 'VkDevice':
                return True
            handle = handle.parent
        return False

    def allComments(self, lines: list[str]) -> bool:
        not_empty_lines = [line for line in lines.splitlines() if line.strip()]
        return all(line.startswith('//') for line in not_empty_lines)

    def generate(self):
        self.write(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
            // See {os.path.basename(__file__)} for modifications

            /***************************************************************************
            *
            * Copyright (c) 2015-2025 The Khronos Group Inc.
            * Copyright (c) 2015-2025 Valve Corporation
            * Copyright (c) 2015-2025 LunarG, Inc.
            * Copyright (c) 2015-2025 Google Inc.
            * Copyright (c) 2015-2025 RasterGrid Kft.
            *
            * Licensed under the Apache License, Version 2.0 (the "License");
            * you may not use this file except in compliance with the License.
            * You may obtain a copy of the License at
            *
            *     http://www.apache.org/licenses/LICENSE-2.0
            *
            * Unless required by applicable law or agreed to in writing, software
            * distributed under the License is distributed on an "AS IS" BASIS,
            * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
            * See the License for the specific language governing permissions and
            * limitations under the License.
            ****************************************************************************/\n''')
        self.write('// NOLINTBEGIN') # Wrap for clang-tidy to ignore

        if self.filename == 'object_tracker_device_methods.h':
            self.generateDeviceHeader()
        elif self.filename == 'object_tracker_instance_methods.h':
            self.generateInstanceHeader()
        elif self.filename == 'object_tracker.cpp':
            self.generateSource()
        else:
            self.write(f'\nFile name {self.filename} has no code to generate\n')

        self.write('// NOLINTEND') # Wrap for clang-tidy to ignore


    def generateHeader(self, want_instance):
        out = []
        guard_helper = PlatformGuardHelper()
        for command in [x for x in self.vk.commands.values() if x.instance == want_instance]:
            out.extend(guard_helper.add_guard(command.protect))
            (pre_call_validate, pre_call_record, post_call_record) = self.generateFunctionBody(command)

            prototype = (command.cPrototype.split('VKAPI_CALL ')[1])[2:-1]
            terminator = ';\n' if 'ValidationCache' in command.name else ' override;\n'

            # If a function has manual implementation we still need to generate its signature in the header file
            function_signature_for_no_autogen = command.name in self.no_autogen_list and not command.name == 'vkCreateInstance'

            generate_pre_call_validate = (pre_call_validate and not self.allComments(pre_call_validate)) or function_signature_for_no_autogen

            if generate_pre_call_validate or command.name in self.no_validate_autogen_list:
                prePrototype = prototype.replace(')', ', const ErrorObject& error_obj)')
                out.append(f'bool PreCallValidate{prePrototype} const{terminator}')

            prototype = prototype.replace(')', ', const RecordObject& record_obj)')
            if pre_call_record or command.name in self.no_pre_record_autogen_list:
                out.append(f'void PreCallRecord{prototype}{terminator}')

            if post_call_record or command.name in self.no_post_record_autogen_list:
                if command.name in self.post_call_record_additional_parameters:
                    post_call_record_additional_parameters = self.post_call_record_additional_parameters[command.name]
                    prototype = prototype.replace(')', f', {post_call_record_additional_parameters})')
                out.append(f'void PostCallRecord{prototype}{terminator}')

        out.extend(guard_helper.add_guard(None))
        self.write("".join(out))

    def generateDeviceHeader(self):
        self.generateHeader(False)
        out = []
        # These are Post/Pre call that normally would not be created but we need have manual object tracking logic for them
        out.append('''
            void PreCallRecordResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags, const RecordObject& record_obj) override;
            void PreCallRecordFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers, const RecordObject& record_obj) override;
            void PreCallRecordFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets, const RecordObject& record_obj) override;
            ''')
        self.write("".join(out))

    def generateInstanceHeader(self):
        self.generateHeader(True)
        out = []
        # These are Post/Pre call that normally would not be created but we need have manual object tracking logic for them
        out.append('''

            void PostCallRecordDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties *pQueueFamilyProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties2 *pQueueFamilyProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceQueueFamilyProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties2 *pQueueFamilyProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlanePropertiesKHR* pProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceDisplayPlaneProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlaneProperties2KHR* pProperties, const RecordObject& record_obj) override;
            ''')
        self.write("".join(out))

    def generateSource(self):
        out = []
        out.append('''
#include "object_tracker/object_lifetime_validation.h"

namespace object_lifetimes {
ReadLockGuard Device::ReadLock() const { return ReadLockGuard(validation_object_mutex, std::defer_lock); }
WriteLockGuard Device::WriteLock() { return WriteLockGuard(validation_object_mutex, std::defer_lock); }

// ObjectTracker undestroyed objects validation function
bool Instance::ReportUndestroyedObjects(const Location& loc) const {
    bool skip = false;
    const std::string error_code = "VUID-vkDestroyInstance-instance-00629";
''')
        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and not self.isParentDevice(x)]:
            comment_prefix = ''
            if APISpecific.IsImplicitlyDestroyed(self.targetApiName, handle.name):
                comment_prefix = '// No destroy API or implicitly freed/destroyed -- do not report: '
            out.append(f'    {comment_prefix}skip |= ReportLeakedObjects(kVulkanObjectType{handle.name[2:]}, error_code, loc);\n')
        out.append('    return skip;\n')
        out.append('}\n')

        out.append('''
bool Device::ReportUndestroyedObjects(const Location& loc) const {
    bool skip = false;
    const std::string error_code = "VUID-vkDestroyDevice-device-05137";
''')

        comment_prefix = ''
        if APISpecific.IsImplicitlyDestroyed(self.targetApiName, 'VkCommandBuffer'):
            comment_prefix = '// No destroy API or implicitly freed/destroyed -- do not report: '
        out.append(f'    {comment_prefix}skip |= ReportLeakedObjects(kVulkanObjectTypeCommandBuffer, error_code, loc);\n')

        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and self.isParentDevice(x)]:
            comment_prefix = ''
            if APISpecific.IsImplicitlyDestroyed(self.targetApiName, handle.name):
                comment_prefix = '// No destroy API or implicitly freed/destroyed -- do not report: '
            out.append(f'    {comment_prefix}skip |= ReportLeakedObjects(kVulkanObjectType{handle.name[2:]}, error_code, loc);\n')
        out.append('    return skip;\n')
        out.append('}\n')

        out.append('\nvoid Instance::DestroyLeakedObjects() {\n')
        out.append('    const Location loc = Func::vkDestroyInstance;\n')
        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and not self.isParentDevice(x)]:
            out.append(f'    DestroyUndestroyedObjects(kVulkanObjectType{handle.name[2:]}, loc);\n')
        out.append('}\n')

        out.append('\nvoid Device::DestroyLeakedObjects() {\n')
        out.append('    const Location loc = Func::vkDestroyDevice;\n')
        out.append('    DestroyUndestroyedObjects(kVulkanObjectTypeCommandBuffer, loc);\n')
        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and self.isParentDevice(x)]:
            out.append(f'    DestroyUndestroyedObjects(kVulkanObjectType{handle.name[2:]}, loc);\n')
        out.append('}\n')

        guard_helper = PlatformGuardHelper()
        for command in [x for x in self.vk.commands.values() if x.name not in self.no_autogen_list]:
            out.extend(guard_helper.add_guard(command.protect))

            class_name = "Device" if not command.instance else "Instance"

            # Generate object handling code
            (pre_call_validate, pre_call_record, post_call_record) = self.generateFunctionBody(command)

            prototype = (command.cPrototype.split('VKAPI_CALL ')[1])[2:-1]

            # Output PreCallValidateAPI function if necessary
            if pre_call_validate and command.name not in self.no_validate_autogen_list:
                prePrototype = prototype.replace(')', ', const ErrorObject& error_obj)')
                if self.allComments(pre_call_validate):
                     out.append(f'''
                        // {command.name}:
                        {pre_call_validate}
                        ''')
                elif command.alias:
                    # For alias that are promoted, just point to new function, ErrorObject will allow us to distinguish the caller
                    paramList = [param.name for param in command.params]
                    paramList.append('error_obj')
                    params = ', '.join(paramList)
                    out.append(f'''
                        bool {class_name}::PreCallValidate{prePrototype} const {{
                            return PreCallValidate{command.alias[2:]}({params});
                        }}
                        ''')
                else:
                    out.append(f'''
                        bool {class_name}::PreCallValidate{prePrototype} const {{
                            bool skip = false;
                            {pre_call_validate}
                            return skip;
                        }}
                        ''')

            # Output PreCallRecordAPI function if necessary
            if pre_call_record and command.name not in self.no_pre_record_autogen_list:
                postPrototype = prototype.replace(')', ', const RecordObject& record_obj)')
                out.append(f'''
                    void {class_name}::PreCallRecord{postPrototype} {{
                        {pre_call_record}
                    }}
                    ''')

            # Output PostCallRecordAPI function if necessary
            if post_call_record and command.name not in self.no_post_record_autogen_list:
                out.append('\n')
                postPrototype = f'void {class_name}::PostCallRecord{prototype} {{\n'
                postPrototype = postPrototype.replace(')', ', const RecordObject& record_obj)')
                if command.returnType == 'VkResult':
                    # Some commands can have partial valid handles be created
                    partial_success_commands = ['vkCreateGraphicsPipelines', 'vkCreateComputePipelines', 'vkCreateRayTracingPipelinesNV', 'vkCreateRayTracingPipelinesKHR', 'vkCreateShadersEXT']
                    if command.name not in partial_success_commands:
                        postPrototype = postPrototype.replace('{', '{\n    if (record_obj.result < VK_SUCCESS) return;')
                out.append(postPrototype)

                out.append(f'{post_call_record}\n')
                out.append('}\n')

        out.extend(guard_helper.add_guard(None))
        out.append('''
        } // namespace object_lifetimes
        ''')

        self.write("".join(out))

    def structContainsObject(self, struct: Struct) -> bool:
        for member in struct.members:
            if member.type in self.vk.handles:
                return True
            # recurse for member structs, guard against infinite recursion
            elif member.type in self.vk.structs and member.type != struct.name:
                if self.structContainsObject(self.vk.structs[member.type]):
                    return True
        return False

    def getAllocVUID(self, param: Param, allocType: str) -> str:
        # Do not report allocation callback VUIDs if the target API does not support them
        if not APISpecific.AreAllocVUIDsEnabled(self.targetApiName):
            return "kVUIDUndefined"

        lookup_string = '%s-%s' %(param.name, allocType)
        vuid = self.manual_vuids.get(lookup_string, None)
        if vuid is not None:
            return vuid
        lookup_string = '%s-%s-%s' %(param.type, param.name, allocType)
        vuid = self.manual_vuids.get(lookup_string, None)
        if vuid is not None:
            return vuid
        return "kVUIDUndefined"


    def getParamVUID(self, member: Member, parentName: str) -> str:
        # Exceptions
        if (member.name == 'pCounterBuffers'):
            if parentName == 'vkCmdBeginTransformFeedbackEXT':
                return '"VUID-vkCmdBeginTransformFeedbackEXT-counterBufferCount-02607"'
            if parentName == 'vkCmdEndTransformFeedbackEXT':
                return '"VUID-vkCmdEndTransformFeedbackEXT-counterBufferCount-02608"'

        # Replace with alias if one
        alias = self.vk.commands[parentName].alias if parentName in self.vk.commands else None
        parent = alias if alias else parentName
        vuid_string = f'VUID-{parent}-{member.name}-parameter'

        # TODO: Currently just brute force check all VUs, but should be smarter what makes these `-parameter` VUs
        param_vuid = f'"{vuid_string}"' if vuid_string in self.valid_vuids else "kVUIDUndefined"
        return param_vuid

    def hasFieldParentVUID(self, member: Member, structName: str) -> bool:
        # Not a vulkan handle. Parent VUIDs are only for vulkan handles
        if member.type not in self.vk.handles:
            return False

        # All struct members that are vulkan handles should have parent VUID.
        # There is a bunch of structs though, for which the specification does not do this.
        return structName not in self.structs_that_forgot_about_parent_vuids


    def hasParameterParentVUID(self, parameter: Member, commandName: str) -> bool:
        # Check for commands that, except the first dispatchable parameter,
        # do not have other parameters that are Vulkan handles.
        # Such commands can't have parent VUIDs (e.g. vkQueueWaitIdle)
        params = self.vk.commands[commandName].params
        only_dispatchable_parameter = len([x for x in params if x.type in self.vk.handles and (not x.pointer or x.const)]) == 1
        if only_dispatchable_parameter:
            return False

        # Special case: vkReleaseFullScreenExclusiveModeEXT.
        # The specification does not define a parent VUID for the swapchain parameter.
        # It mentions in a free form that device should be associated with a swapchain.
        if commandName == 'vkReleaseFullScreenExclusiveModeEXT':
            return False

        # Not a vulkan handle. Parent VUIDs are only for vulkan handles
        if parameter.type not in self.vk.handles:
            return False

        # Skip output parameter
        if parameter.pointer and not parameter.const:
            return False

        # Non-dispatchable handles need parent
        if not self.vk.handles[parameter.type].dispatchable:
            return True

        # Queue/command buffer handles have parent vuids
        if parameter.type == 'VkQueue' or parameter.type == 'VkCommandBuffer':
            return True

        # For other dispatchable handles it depends on the API function
        return commandName in self.dispatchable_has_parent_vuid_commands

    # It is very complex for the spec handle walking through structs and finding Handles and generating implicit VUs,
    # We instead just have to do it manually for now.
    # (details at https://gitlab.khronos.org/vulkan/vulkan/-/issues/3553#note_424431)
    #
    # This was attempted to be solved in https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/6711
    # but we need a more automatic way, but having this here helps as a working reference in the future
    def getManualParentVUID(self, memberName: str, structName: str, commandName: str):
        # These are cases where there is only a single caller of the struct
        # We check by command name incase a new command would use the struct
        if commandName == 'vkCreateImageView' and memberName == 'image':
            return '"VUID-vkCreateImageView-image-09179"'
        if commandName =='vkGetPipelineExecutablePropertiesKHR' and memberName == 'pipeline':
            return '"VUID-vkGetPipelineExecutablePropertiesKHR-pipeline-03271"'
        if commandName == 'vkCmdCudaLaunchKernelNV' and memberName == 'function':
            return '"UNASSIGNED-VkCudaLaunchInfoNV-function-parent"'
        if commandName == 'vkLatencySleepNV' and memberName == 'signalSemaphore':
            return '"UNASSIGNED-VkLatencySleepInfoNV-signalSemaphore-parent"'
        if commandName == 'vkCreateCudaFunctionNV' and memberName == 'module':
            return '"UNASSIGNED-VkCudaFunctionCreateInfoNV-module-parent"'
        if (commandName == 'vkCmdPushConstants2' or commandName == 'vkCmdPushConstants2KHR') and memberName == 'layout':
            return '"UNASSIGNED-VkPushConstantsInfo-layout-parent"'
        if commandName == 'vkCmdSetDescriptorBufferOffsets2EXT' and memberName == 'layout':
            return '"UNASSIGNED-VkSetDescriptorBufferOffsetsInfoEXT-layout-parent"'
        if commandName == 'vkCmdBindDescriptorBufferEmbeddedSamplers2EXT' and memberName == 'layout':
            return '"UNASSIGNED-VkBindDescriptorBufferEmbeddedSamplersInfoEXT-layout-parent"'
        if commandName == 'vkCreateBufferView' and memberName == 'buffer':
            return '"UNASSIGNED-VkBufferViewCreateInfo-buffer-parent"'
        if commandName == 'vkCreateShadersEXT' and memberName == 'pSetLayouts':
            return '"UNASSIGNED-VkShaderCreateInfoEXT-pSetLayouts-parent"'
        if commandName == 'vkGetMemoryWin32HandleKHR' and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryGetWin32HandleInfoKHR-memory-parent"'
        if commandName == 'vkImportSemaphoreWin32HandleKHR' and memberName == 'semaphore':
            return '"UNASSIGNED-VkImportSemaphoreWin32HandleInfoKHR-semaphore-parent"'
        if commandName == 'vkGetSemaphoreWin32HandleKHR' and memberName == 'semaphore':
            return '"UNASSIGNED-VkSemaphoreGetWin32HandleInfoKHR-semaphore-parent"'
        if commandName == 'vkImportFenceWin32HandleKHR' and memberName == 'fence':
            return '"UNASSIGNED-VkImportFenceWin32HandleInfoKHR-fence-parent"'
        if commandName == 'vkGetFenceWin32HandleKHR' and memberName == 'fence':
            return '"UNASSIGNED-VkFenceGetWin32HandleInfoKHR-fence-parent"'
        if commandName == 'vkGetMemoryFdKHR' and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryGetFdInfoKHR-memory-parent"'
        if commandName == 'vkImportSemaphoreFdKHR' and memberName == 'semaphore':
            return '"UNASSIGNED-VkImportSemaphoreFdInfoKHR-semaphore-parent"'
        if commandName == 'vkGetSemaphoreFdKHR' and memberName == 'semaphore':
            return '"UNASSIGNED-VkSemaphoreGetFdInfoKHR-semaphore-parent"'
        if commandName == 'vkImportFenceFdKHR' and memberName == 'fence':
            return '"UNASSIGNED-VkImportFenceFdInfoKHR-fence-parent"'
        if commandName == 'vkGetFenceFdKHR' and memberName == 'fence':
            return '"UNASSIGNED-VkFenceGetFdInfoKHR-fence-parent"'
        if commandName == 'vkGetMemoryAndroidHardwareBufferANDROID' and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryGetAndroidHardwareBufferInfoANDROID-memory-parent"'
        if commandName == 'vkGetMemoryZirconHandleFUCHSIA' and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryGetZirconHandleInfoFUCHSIA-memory-parent"'
        if commandName == 'vkImportSemaphoreZirconHandleFUCHSIA' and memberName == 'semaphore':
            return '"UNASSIGNED-VkImportSemaphoreZirconHandleInfoFUCHSIA-semaphore-parent"'
        if commandName == 'vkGetSemaphoreZirconHandleFUCHSIA' and memberName == 'semaphore':
            return '"UNASSIGNED-VkSemaphoreGetZirconHandleInfoFUCHSIA-semaphore-parent"'
        if commandName == 'vkGetMemoryRemoteAddressNV' and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryGetRemoteAddressInfoNV-memory-parent"'
        if commandName == 'vkAllocateMemory' and memberName == 'collection':
            return '"UNASSIGNED-VkImportMemoryBufferCollectionFUCHSIA-collection-parent"'
        if commandName == 'vkCreateBuffer' and memberName == 'collection':
            return '"UNASSIGNED-VkBufferCollectionBufferCreateInfoFUCHSIA-collection-parent"'
        if commandName == 'vkCreateImage' and memberName == 'collection':
            return '"UNASSIGNED-VkBufferCollectionImageCreateInfoFUCHSIA-collection-parent"'
        if commandName == 'vkGetDescriptorSetLayoutHostMappingInfoVALVE' and memberName == 'descriptorSetLayout':
            return '"UNASSIGNED-VkDescriptorSetBindingReferenceVALVE-descriptorSetLayout-parent"'
        if commandName == 'vkGetAccelerationStructureMemoryRequirementsNV' and memberName == 'accelerationStructure':
            return '"UNASSIGNED-VkAccelerationStructureMemoryRequirementsInfoNV-accelerationStructure-parent"'
        if commandName == 'vkGetPipelineIndirectDeviceAddressNV' and memberName == 'pipeline':
            return '"UNASSIGNED-VkPipelineIndirectDeviceAddressInfoNV-pipeline-parent"'
        if commandName == 'vkCreateCuFunctionNVX' and memberName == 'module':
            return '"UNASSIGNED-VkCuFunctionCreateInfoNVX-module-parent"'
        if commandName == 'vkCmdCuLaunchKernelNVX' and memberName == 'function':
            return '"UNASSIGNED-VkCuLaunchInfoNVX-function-parent"'
        if commandName == 'vkCreateIndirectCommandsLayoutNV' and memberName == 'pushconstantPipelineLayout':
            return '"UNASSIGNED-VkIndirectCommandsLayoutTokenNV-pushconstantPipelineLayout-parent"'
        if commandName == 'vkGetBufferOpaqueCaptureDescriptorDataEXT' and memberName == 'buffer':
            return '"UNASSIGNED-VkBufferCaptureDescriptorDataInfoEXT-buffer-parent"'
        if commandName == 'vkGetImageOpaqueCaptureDescriptorDataEXT' and memberName == 'image':
            return '"UNASSIGNED-VkImageCaptureDescriptorDataInfoEXT-image-parent"'
        if commandName == 'vkGetImageViewOpaqueCaptureDescriptorDataEXT' and memberName == 'imageView':
            return '"UNASSIGNED-VkImageViewCaptureDescriptorDataInfoEXT-imageView-parent"'
        if commandName == 'vkGetSamplerOpaqueCaptureDescriptorDataEXT' and memberName == 'sampler':
            return '"UNASSIGNED-VkSamplerCaptureDescriptorDataInfoEXT-sampler-parent"'
        if commandName == 'vkBindVideoSessionMemoryKHR' and memberName == 'memory':
            return '"UNASSIGNED-VkBindVideoSessionMemoryInfoKHR-memory-parent"'
        if commandName == 'vkCmdDecodeVideoKHR' and memberName == 'srcBuffer':
            return '"UNASSIGNED-VkVideoDecodeInfoKHR-srcBuffer-parent"'
        if commandName == 'vkGetEncodedVideoSessionParametersKHR' and memberName == 'videoSessionParameters':
            return '"UNASSIGNED-VkVideoEncodeSessionParametersGetInfoKHR-videoSessionParameters-parent"'
        if commandName == 'vkCmdEncodeVideoKHR' and memberName == 'dstBuffer':
            return '"UNASSIGNED-VkVideoEncodeInfoKHR-dstBuffer-parent"'
        if commandName == 'vkCreateDisplayPlaneSurfaceKHR' and memberName == 'displayMode':
            return '"UNASSIGNED-VkDisplaySurfaceCreateInfoKHR-displayMode-parent"'
        if commandName == 'vkGetDisplayPlaneCapabilities2KHR' and memberName == 'mode':
            return '"UNASSIGNED-VkDisplayPlaneInfo2KHR-mode-parent"'
        if commandName == 'vkCmdBindDescriptorBuffersEXT' and memberName == 'buffer':
            return '"UNASSIGNED-VkDescriptorBufferBindingPushDescriptorBufferHandleEXT-buffer-parent"'
        if commandName == 'vkReleaseSwapchainImagesEXT' and memberName == 'swapchain':
            return '"UNASSIGNED-VkReleaseSwapchainImagesInfoEXT-swapchain-parent"'
        if commandName == 'vkCmdBeginConditionalRenderingEXT' and memberName == 'buffer':
            return '"UNASSIGNED-VkConditionalRenderingBeginInfoEXT-buffer-parent"'
        if (commandName == 'vkMapMemory2' or commandName == 'vkMapMemory2KHR') and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryMapInfo-memory-parent"'
        if (commandName == 'vkUnmapMemory2' or commandName == 'vkUnmapMemory2KHR') and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryUnmapInfo-memory-parent"'
        if (commandName == 'vkCopyMemoryToImage' or commandName == 'vkCopyMemoryToImageEXT') and memberName == 'dstImage':
            return '"UNASSIGNED-VkCopyMemoryToImageInfo-dstImage-parent"'
        if (commandName == 'vkCopyImageToMemory' or commandName == 'vkCopyImageToMemoryEXT') and memberName == 'srcImage':
            return '"UNASSIGNED-VkCopyImageToMemoryInfo-srcImage-parent"'
        if (commandName == 'vkTransitionImageLayout' or commandName == 'vkTransitionImageLayoutEXT') and memberName == 'image':
            return '"UNASSIGNED-VkHostImageLayoutTransitionInfo-image-parent"'
        if commandName == 'vkCreateMicromapEXT' and memberName == 'buffer':
            return '"UNASSIGNED-VkMicromapCreateInfoEXT-buffer-parent"'
        if commandName == 'vkCreateAccelerationStructureKHR' and memberName == 'buffer':
            return '"UNASSIGNED-VkAccelerationStructureCreateInfoKHR-buffer-parent"'
        if commandName == 'vkCreateImage' and memberName == 'swapchain':
            return '"UNASSIGNED-VkImageSwapchainCreateInfoKHR-swapchain-parent"'
        if commandName == 'vkQueuePresentKHR' and memberName == 'pFences':
            return '"UNASSIGNED-VkSwapchainPresentFenceInfoEXT-pFences-parent"'
        if commandName == 'vkGetAccelerationStructureDeviceAddressKHR' and memberName == 'accelerationStructure':
            return '"UNASSIGNED-VkAccelerationStructureDeviceAddressInfoKHR-accelerationStructure-parent"'
        if commandName == 'vkCreatePipelineBinariesKHR' and memberName == 'pipeline':
            return '"UNASSIGNED-VkPipelineBinaryCreateInfoKHR-pipeline-parent"'
        if commandName == 'vkGetPipelineBinaryDataKHR' and memberName == 'pipelineBinary':
            return '"UNASSIGNED-VkPipelineBinaryDataInfoKHR-pipelineBinary-parent"'
        if commandName == 'vkReleaseCapturedPipelineDataKHR' and memberName == 'pipeline':
            return '"UNASSIGNED-VkReleaseCapturedPipelineDataInfoKHR-pipeline-parent"'
        if commandName.startswith('vkWaitSemaphores') and memberName == 'pSemaphores':
            return '"UNASSIGNED-VkSemaphoreWaitInfo-pSemaphores-parent"'
        if commandName.startswith('vkSignalSemaphore') and memberName == 'semaphore':
            return '"UNASSIGNED-VkSemaphoreSignalInfo-semaphore-parent"'
        if commandName.startswith('vkGetImageMemoryRequirements2') and memberName == 'image':
            return '"UNASSIGNED-VkSemaphoreSignalInfo-image-parent"'
        if commandName.startswith('vkGetBufferMemoryRequirements2') and memberName == 'buffer':
            return '"UNASSIGNED-VkSemaphoreSignalInfo-buffer-parent"'
        if commandName.startswith('vkGetImageSparseMemoryRequirements2') and memberName == 'image':
            return '"UNASSIGNED-VkSemaphoreSignalInfo-image-parent"'
        if commandName.startswith('vkQueueSubmit2') and memberName == 'commandBuffer':
            return '"UNASSIGNED-VkCommandBufferSubmitInfo-commandBuffer-parent"'
        if commandName.startswith('vkBindImageMemory2') and memberName == 'swapchain':
            return '"UNASSIGNED-VkBindImageMemorySwapchainInfoKHR-swapchain-parent"'
        if commandName.startswith('vkGetDeviceMemoryOpaqueCaptureAddress') and memberName == 'memory':
            return '"UNASSIGNED-VkDeviceMemoryOpaqueCaptureAddressInfo-memory-parent"'

        # Same as above, but memberName has naming collision so need to use struct name as well
        if commandName == 'vkCreateGraphicsPipelines' and structName == 'VkGraphicsPipelineShaderGroupsCreateInfoNV' and memberName == 'pPipelines':
            return '"UNASSIGNED-VkGraphicsPipelineShaderGroupsCreateInfoNV-pPipelines-parent"'

        # These are cases where multiple commands call the struct
        if structName == 'VkPipelineExecutableInfoKHR' and memberName == 'pipeline':
            if commandName == 'vkGetPipelineExecutableStatisticsKHR':
                return '"VUID-vkGetPipelineExecutableStatisticsKHR-pipeline-03273"'
            elif commandName == 'vkGetPipelineExecutableInternalRepresentationsKHR':
                return '"VUID-vkGetPipelineExecutableInternalRepresentationsKHR-pipeline-03277"'

        # These are cases are also where multiple commands call the struct,
        # but for simplicity, use same VUID because the Location will provide the name of the caller.
        # The only reason these have seperate VUs is because they were listed in the command, not the struct
        if structName == 'VkRenderingFragmentDensityMapAttachmentInfoEXT' and memberName == 'imageView':
            return '"UNASSIGNED-VkRenderingFragmentDensityMapAttachmentInfoEXT-imageView-commonparent"'
        if structName == 'VkRenderingFragmentShadingRateAttachmentInfoKHR' and memberName == 'imageView':
            return '"UNASSIGNED-VkRenderingFragmentShadingRateAttachmentInfoKHR-imageView-commonparent"'
        if structName == 'VkSubpassShadingPipelineCreateInfoHUAWEI' and memberName == 'renderPass':
            return '"UNASSIGNED-VkSubpassShadingPipelineCreateInfoHUAWEI-renderPass-parent"'
        if structName == 'VkIndirectCommandsStreamNV' and memberName == 'buffer':
            return '"UNASSIGNED-VkIndirectCommandsStreamNV-buffer-parent"'
        if structName == 'VkPipelineLayoutCreateInfo' and memberName == 'pSetLayouts':
            return '"UNASSIGNED-VkPipelineLayoutCreateInfo-pSetLayouts-commonparent"'
        if structName == 'VkVideoInlineQueryInfoKHR' and memberName == 'queryPool':
            return '"UNASSIGNED-VkVideoInlineQueryInfoKHR-queryPool-parent"'
        if structName == 'VkMappedMemoryRange' and memberName == 'memory':
            return '"UNASSIGNED-VkMappedMemoryRange-memory-device"'
        if structName == 'VkPipelineShaderStageCreateInfo' and memberName == 'module':
            return '"UNASSIGNED-VkPipelineShaderStageCreateInfo-module-parent"'
        if structName == 'VkVideoPictureResourceInfoKHR' and memberName == 'imageViewBinding':
            return '"UNASSIGNED-VkVideoPictureResourceInfoKHR-imageViewBinding-parent"'
        if structName == 'VkGeometryAABBNV' and memberName == 'aabbData':
            return '"UNASSIGNED-VkGeometryAABBNV-aabbData-parent"'
        if structName == 'VkPipelineLibraryCreateInfoKHR' and memberName == 'pLibraries':
            return '"UNASSIGNED-VkPipelineLibraryCreateInfoKHR-pLibraries-parent"'
        if structName == 'VkCopyMicromapToMemoryInfoEXT' and memberName == 'src':
            return '"UNASSIGNED-VkCopyMicromapToMemoryInfoEXT-src-parent"'
        if structName == 'VkCopyMemoryToMicromapInfoEXT' and memberName == 'dst':
            return '"UNASSIGNED-VkCopyMemoryToMicromapInfoEXT-dst-parent"'
        if structName == 'VkCopyAccelerationStructureToMemoryInfoKHR' and memberName == 'src':
            return '"UNASSIGNED-VkCopyAccelerationStructureToMemoryInfoKHR-src-parent"'
        if structName == 'VkCopyMemoryToAccelerationStructureInfoKHR' and memberName == 'dst':
            return '"UNASSIGNED-VkCopyMemoryToAccelerationStructureInfoKHR-dst-parent"'
        if structName == 'VkSamplerYcbcrConversionInfo' and memberName == 'conversion':
            return '"UNASSIGNED-VkSamplerYcbcrConversionInfo-conversion-parent"'
        if structName == 'VkShaderModuleValidationCacheCreateInfoEXT' and memberName == 'validationCache':
            return '"UNASSIGNED-VkShaderModuleValidationCacheCreateInfoEXT-validationCache-parent"'
        if structName == 'VkBufferDeviceAddressInfo' and memberName == 'buffer':
            return '"UNASSIGNED-VkBufferDeviceAddressInfo-buffer-parent"'
        if structName == 'VkPipelineBinaryInfoKHR' and memberName == 'pPipelineBinaries':
            return '"UNASSIGNED-VkPipelineBinaryInfoKHR-pPipelineBinaries-parent"'
        if structName == 'VkGeneratedCommandsPipelineInfoEXT' and memberName == 'pipeline':
            return '"UNASSIGNED-VkGeneratedCommandsPipelineInfoEXT-pipeline-parent"'
        if structName == 'VkGeneratedCommandsShaderInfoEXT' and memberName == 'pShaders':
            return '"UNASSIGNED-VkGeneratedCommandsShaderInfoEXT-pShaders-parent"'
        if structName == 'VkIndirectCommandsLayoutCreateInfoEXT' and memberName == 'pipelineLayout':
            return '"UNASSIGNED-VkIndirectCommandsLayoutCreateInfoEXT-pipelineLayout-parent"'
        if structName == 'VkIndirectExecutionSetPipelineInfoEXT' and memberName == 'initialPipeline':
            return '"UNASSIGNED-VkIndirectExecutionSetPipelineInfoEXT-initialPipeline-parent"'
        if structName == 'VkIndirectExecutionSetShaderInfoEXT' and memberName == 'pInitialShaders':
            return '"UNASSIGNED-VkIndirectExecutionSetShaderInfoEXT-pInitialShaders-parent"'
        if structName == 'VkIndirectExecutionSetShaderLayoutInfoEXT' and memberName == 'pSetLayouts':
            return '"UNASSIGNED-VkIndirectExecutionSetShaderLayoutInfoEXT-pSetLayouts-parent"'
        if structName == 'VkWriteIndirectExecutionSetPipelineEXT' and memberName == 'pipeline':
            return '"UNASSIGNED-VkWriteIndirectExecutionSetPipelineEXT-pipeline-parent"'
        if structName == 'VkWriteIndirectExecutionSetShaderEXT' and memberName == 'shader':
            return '"UNASSIGNED-VkWriteIndirectExecutionSetShaderEXT-shader-parent"'
        if structName == 'VkDescriptorDataEXT' and memberName == 'pSampler':
            return '"UNASSIGNED-VkDescriptorDataEXT-pSampler-parent"'
        if structName == 'VkVideoEncodeQuantizationMapInfoKHR' and memberName == 'quantizationMap':
            return '"UNASSIGNED-VkVideoEncodeQuantizationMapInfoKHR-quantizationMap-parent"'
        if structName == 'VkPipelineInfoKHR' and memberName == 'pipeline':
            return '"UNASSIGNED-VkPipelineInfoKHR-pipeline-parent"'
        if structName == 'VkMemoryGetMetalHandleInfoEXT' and memberName == 'memory':
            return '"UNASSIGNED-VkMemoryGetMetalHandleInfoEXT-memory-parent"'
        if structName == 'VkTileMemoryBindInfoQCOM' and memberName == 'memory':
            return '"UNASSIGNED-VkTileMemoryBindInfoQCOM-memory-parent"'
        if structName == 'VkExternalComputeQueueCreateInfoNV' and memberName == 'preferredQueue':
            return '"UNASSIGNED-VkExternalComputeQueueCreateInfoNV-preferredQueue-parent"'
        if structName == 'VkFrameBoundaryTensorsARM' and memberName == 'pTensors':
            return '"UNASSIGNED-VkFrameBoundaryTensorsARM-pTensors-parent"'
        if structName == 'VkTensorMemoryBarrierARM' and memberName == 'tensor':
            return '"UNASSIGNED-VkTensorMemoryBarrierARM-tensor-parent"'
        if structName == 'VkMemoryDedicatedAllocateInfoTensorARM' and memberName == 'tensor':
            return '"UNASSIGNED-VkMemoryDedicatedAllocateInfoTensorARM-tensor-parent"'
        if structName == 'VkDescriptorGetTensorInfoARM' and memberName == 'tensorView':
            return '"UNASSIGNED-VkDescriptorGetTensorInfoARM-tensorView-parent"'
        if structName == 'VkTensorViewCreateInfoARM' and memberName == 'tensor':
            return '"UNASSIGNED-VkTensorViewCreateInfoARM-tensor-parent"'
        if structName == 'VkTensorMemoryRequirementsInfoARM' and memberName == 'tensor':
            return '"UNASSIGNED-VkTensorMemoryRequirementsInfoARM-tensor-parent"'
        if structName == 'VkTensorCaptureDescriptorDataInfoARM' and memberName == 'tensor':
            return '"UNASSIGNED-VkTensorCaptureDescriptorDataInfoARM-tensor-parent"'
        if structName == 'VkTensorViewCaptureDescriptorDataInfoARM' and memberName == 'tensorView':
            return '"UNASSIGNED-VkTensorViewCaptureDescriptorDataInfoARM-tensorView-parent"'

        # Common parents because the structs have more then one handle that needs to be check
        if (structName == 'VkBufferMemoryBarrier' and memberName == 'buffer') or (structName == 'VkImageMemoryBarrier' and memberName == 'image'):
            if commandName == 'vkCmdPipelineBarrier':
                return '"UNASSIGNED-vkCmdPipelineBarrier-commandBuffer-commonparent"'
            elif commandName == 'vkCmdWaitEvents':
                return '"UNASSIGNED-vkCmdWaitEvents-commandBuffer-commonparent"'
        if (structName == 'VkBufferMemoryBarrier2' and memberName == 'buffer') or (structName == 'VkImageMemoryBarrier2' and memberName == 'image'):
            if commandName.startswith('vkCmdPipelineBarrier2'):
                return '"UNASSIGNED-vkCmdPipelineBarrier2-commandBuffer-commonparent"'
            elif commandName.startswith('vkCmdWaitEvents2'):
                return '"UNASSIGNED-vkCmdWaitEvents2-commandBuffer-commonparent"'
            elif commandName.startswith('vkCmdSetEvent2'):
                return '"UNASSIGNED-vkCmdSetEvent2-commandBuffer-commonparent"'

        # Single command calls same struct through 2 different structs
        if commandName == 'vkQueueBindSparse':
            if structName == 'VkSparseMemoryBind' and memberName == 'memory':
                return '"UNASSIGNED-VkSparseMemoryBind-memory-parent"'
            if structName == 'VkSparseImageMemoryBind' and memberName == 'memory':
                return '"UNASSIGNED-VkSparseImageMemoryBind-memory-parent"'
            if structName == 'VkSparseBufferMemoryBindInfo' and memberName == 'buffer':
                return '"UNASSIGNED-VkSparseBufferMemoryBindInfo-buffer-parent"'
            if structName == 'VkSparseImageOpaqueMemoryBindInfo' and memberName == 'image':
                return '"UNASSIGNED-VkSparseImageOpaqueMemoryBindInfo-image-parent"'
            if structName == 'VkSparseImageMemoryBindInfo' and memberName == 'image':
                return '"UNASSIGNED-VkSparseImageMemoryBindInfo-image-parent"'
        if commandName.startswith('vkQueueSubmit2'):
            if structName == 'VkSemaphoreSubmitInfo' and memberName == 'semaphore':
                return '"UNASSIGNED-VkSemaphoreSubmitInfo-semaphore-parent"'

        # Common parents
        if structName =='VkRenderPassAttachmentBeginInfo' and memberName == 'pAttachments':
            return '"VUID-VkRenderPassBeginInfo-framebuffer-02780"'

        return None

    def getFieldParentVUID(self, member: Member, structName: str, commandName: str, singleParentVuid: bool) -> str:
        if not self.hasFieldParentVUID(member, structName):
            return 'kVUIDUndefined'

        manualVuid = self.getManualParentVUID(member.name, structName, commandName)
        if  manualVuid is not None:
            return manualVuid
        elif singleParentVuid:
            return getVUID(self.valid_vuids, f'VUID-{structName}-{member.name}-parent')
        else:
            return getVUID(self.valid_vuids, f'VUID-{structName}-commonparent')


    def getParameterParentVUID(self, parameter: Member, commandName: str, singleParentVuid: bool) -> str:
        if not self.hasParameterParentVUID(parameter, commandName):
            return 'kVUIDUndefined'

        # Replace with alias if one
        alias = self.vk.commands[commandName].alias
        parent = alias if alias else commandName

        if singleParentVuid or (commandName in self.use_parent_instead_of_commonparent_commands):
            return getVUID(self.valid_vuids, f'VUID-{parent}-{parameter.name}-parent')
        else:
            return getVUID(self.valid_vuids, f'VUID-{parent}-commonparent')


    # recursively walks struct members (and command params)
    # parentName == Struct or Command calling into this
    # topCommand == The command called from (when in a struct)
    def validateObjects(self, members: list[Member], prefix: str, arrayIndex: int, parentName: str, topCommand: str, errorLoc: str) -> str:
        pre_call_validate = ''
        index = f'index{str(arrayIndex)}'
        arrayIndex += 1
        is_struct = (parentName != topCommand)

        if is_struct:
            handle_types = [x.type for x in members if self.hasFieldParentVUID(x, parentName)]
        else:
            handle_types = [x.type for x in members if self.hasParameterParentVUID(x, parentName)]

        single_parent_vuid = (len(handle_types) == 1)

        # Parent type in parent/commonparent VUIDs: Device, PhysicalDevice or Instance
        parent_type = 'Device'
        for type in handle_types:
            current_type = self.vk.handles[type].parent.type
            if current_type in ['VK_OBJECT_TYPE_PHYSICAL_DEVICE', 'VK_OBJECT_TYPE_DISPLAY_KHR', 'VK_OBJECT_TYPE_DISPLAY_MODE_KHR']:
                parent_type = 'PhysicalDevice'
                # continue search in case instance parent exists
            elif current_type == 'VK_OBJECT_TYPE_INSTANCE':
                parent_type = 'Instance'
                # end search
                break

        # Process any objects in this structure and recurse for any sub-structs in this struct
        for member in members:
            if member.pointer and not member.const:
                continue # ignore output parameters

            if member.type in self.vk.handles:
                if member.noAutoValidity:
                    nullAllowed = 'true'
                elif member.length:
                    nullAllowed = str(member.optionalPointer).lower()
                else:
                    nullAllowed = str(member.optional).lower()

                param_vuid = self.getParamVUID(member, parentName)

                if is_struct:
                    parent_vuid = self.getFieldParentVUID(member, parentName, topCommand, single_parent_vuid)
                else:
                    parent_vuid = self.getParameterParentVUID(member, parentName, single_parent_vuid)

                # Do not generate validation code for the function's dispatchable parameter (the first one).
                # Validation of such parameters is always successful based on the model of how the chassis
                # dispactches the calls to vvl::base::Device. If invalid handle is used it will cause
                # crash/corruption on the chassis level (in GetDispatchKey or later). And if correct handle
                # is passed, then due to the mapping done by GetDispatchKey() the handle will belong to the
                # retrieved validation object, which guarantees positive result of the parenting test.
                function_dispatchable_parameter = not is_struct and member == members[0]
                if function_dispatchable_parameter:
                    chassis_parent_vuid = parent_vuid # keep parent vuid for "Checked by chassis" comment
                    parent_vuid = 'kVUIDUndefined'

                parent_object_type = '' # empty value corresponds to C++ default parameter value (kVulkanObjectTypeDevice)
                if parent_type != 'Device':
                    parent_object_type = f', kVulkanObjectType{parent_type}'

                # Special case: VUID-VkSwapchainCreateInfoKHR-commonparent
                # This is a problematic VUID in the form it's defined in the spec 1.3.265. Details (internal link):
                # https://gitlab.khronos.org/vulkan/vulkan/-/issues/2983
                # Ideally such VUID should be split into two since there are two different parents:
                # VkInstance for surface and VkDevice for oldSwapchain
                if parentName == 'VkSwapchainCreateInfoKHR' and member.name == 'oldSwapchain':
                    parent_object_type = ", kVulkanObjectTypeDevice"

                if member.length:
                    location = f'{errorLoc}.dot(Field::{member.name}, {index})'
                    countName = f'{prefix}{member.length}'
                    pre_call_validate += f'''
                        if (({countName} > 0) && ({prefix}{member.name})) {{
                            for (uint32_t {index} = 0; {index} < {countName}; ++{index}) {{
                                skip |= ValidateObject({prefix}{member.name}[{index}], kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});
                            }}
                        }}\n'''
                elif 'basePipelineHandle' in member.name:
                    pre_call_validate += f'if (({prefix}flags & VK_PIPELINE_CREATE_DERIVATIVE_BIT) && ({prefix}basePipelineIndex == -1))\n'
                    manual_vuid_index = parentName + '-' + member.name
                    param_vuid = self.manual_vuids.get(manual_vuid_index, "kVUIDUndefined")
                    pre_call_validate += f'skip |= ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, false, {param_vuid}, {parent_vuid}, error_obj.location{parent_object_type});\n'
                elif function_dispatchable_parameter:
                    pre_call_validate += f'// Checked by chassis: {member.name}: {param_vuid}\n'
                    if chassis_parent_vuid != 'kVUIDUndefined':
                        pre_call_validate += f'// Checked by chassis: {member.name}: {chassis_parent_vuid}\n'
                elif param_vuid == 'kVUIDUndefined':
                    # These cases are 'commonparent' VUs for "non-ignored parameters"
                    if parent_vuid == 'kVUIDUndefined':
                        location = f'{errorLoc}.dot(Field::{member.name})'
                        if parentName == 'VkPhysicalDeviceSurfaceInfo2KHR':
                            param_vuid = '"VUID-VkPhysicalDeviceSurfaceInfo2KHR-surface-07919"'
                            pre_call_validate += 'if (!IsExtEnabled(extensions.vk_google_surfaceless_query)) {\n'
                        else:
                            pre_call_validate += '// There should be an explicit VU (if not that is a spec bug)\n'
                            pre_call_validate += '{\n'
                        pre_call_validate += f'skip |= ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});\n'
                        pre_call_validate += '}\n'
                else:
                    location = f'{errorLoc}.dot(Field::{member.name})'
                    if self.vk.commands[topCommand].device and self.vk.handles[member.type].instance:
                        # Use case when for device-level API call we should use instance-level validation object
                        pre_call_validate += 'auto instance_object_lifetimes = static_cast<Instance*>(dispatch_instance_->GetValidationObject(container_type));\n'
                        pre_call_validate += f'skip |= instance_object_lifetimes->ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});\n'
                    else:
                        pre_call_validate += f'skip |= ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});\n'

            # Handle Structs that contain objects at some level
            elif member.type in self.vk.structs:
                nested_struct = []
                struct = self.vk.structs[member.type]

                # Structs at first level will have an object
                contains_object = self.structContainsObject(struct)

                # Struct Array
                if member.length is not None:
                    # Update struct prefix
                    nested_struct.append(f'if ({prefix}{member.name}) {{\n')
                    nested_struct.append(f'    for (uint32_t {index} = 0; {index} < {prefix}{member.length}; ++{index}) {{\n')
                    new_error_loc = f'{index}_loc'
                    nested_struct.append(f'[[maybe_unused]] const Location {new_error_loc} = {errorLoc}.dot(Field::{member.name}, {index});\n')
                    new_prefix = f'{prefix}{member.name}[{index}].'
                # Single Struct Pointer
                elif member.pointer:
                    # Update struct prefix
                    new_prefix = f'{prefix}{member.name}->'
                    # Declare safe_VarType for struct
                    nested_struct.append(f'if ({prefix}{member.name}) {{\n')
                    new_error_loc = f'{member.name}_loc'
                    nested_struct.append(f'    [[maybe_unused]] const Location {new_error_loc} = {errorLoc}.dot(Field::{member.name});\n')
                # Single Nested Struct
                else:
                    # Update struct prefix
                    new_prefix = f'{prefix}{member.name}.'
                    new_error_loc = f'{member.name}_loc'
                    nested_struct.append(f'[[maybe_unused]] const Location {new_error_loc} = {errorLoc}.dot(Field::{member.name});\n')
                    # Process sub-structs

                if contains_object:
                    nested_struct.append(self.validateObjects(struct.members, new_prefix, arrayIndex, member.type, topCommand, new_error_loc))

                contains_pNext = False
                if struct.extendedBy:
                    guard_helper = PlatformGuardHelper()
                    for extendedBy in struct.extendedBy:
                        extended_struct = self.vk.structs[extendedBy]
                        extended_members = [x for x in extended_struct.members if x.type in self.vk.handles]
                        if not extended_members:
                            continue
                        contains_pNext = True
                        nested_struct.extend(guard_helper.add_guard(extended_struct.protect))
                        nested_struct.append(f'if ([[maybe_unused]] auto pNext = vku::FindStructInPNextChain<{extendedBy}>({new_prefix}pNext)) {{\n')
                        nested_struct.append(f'    [[maybe_unused]] const Location pNext_loc = {new_error_loc}.pNext(Struct::{extendedBy});\n')
                        nested_struct.append(self.validateObjects(extended_members, 'pNext->', arrayIndex + 1, extendedBy, topCommand, 'pNext_loc'))
                        nested_struct.append('}\n')
                    nested_struct.extend(guard_helper.add_guard(None))
                # Close indentation
                if member.length is not None:
                    nested_struct.append('}\n')
                    nested_struct.append('}\n')
                elif member.pointer:
                    nested_struct.append('}\n')

                # Only print if called into validateObjects
                if contains_object or contains_pNext:
                    pre_call_validate += "".join(nested_struct)

        return pre_call_validate
    #
    # For a particular API, generate the object handling code
    def generateFunctionBody(self, command: Command):
        pre_call_validate = ''
        pre_call_record = ''
        post_call_record = ''
        isGetCreate = 'vkGet' in command.name and command.params[-1].pointer and not command.params[-1].const
        isCreate = any(x in command.name for x in ['Create', 'Allocate', 'Enumerate', 'RegisterDeviceEvent', 'RegisterDisplayEvent', 'AcquirePerformanceConfigurationINTEL']) or isGetCreate
        isDestroy = any(x in command.name for x in ['Destroy', 'Free', 'ReleasePerformanceConfigurationINTEL'])

        # TODO - we need to wrap with autogen list here for header to still build the function definition,
        # but this function is being used in the header to duplicate work to know if the function will be used
        if (command.name not in self.no_autogen_list):
            pre_call_validate += self.validateObjects(command.params, '', 0, command.name, command.name, 'error_obj.location')

        # Handle object create operations if last parameter is created by this call
        if isCreate:
            handle_type = command.params[-1].type
            partial_success_commands = ['vkCreateGraphicsPipelines', 'vkCreateComputePipelines', 'vkCreateRayTracingPipelinesNV', 'vkCreateRayTracingPipelinesKHR', 'vkCreateShadersEXT']
            if handle_type in self.vk.handles:
                # Check for special case where multiple handles are returned
                objectArray = command.params[-1].length is not None

                if objectArray:
                    if command.name in partial_success_commands:
                        post_call_record += 'if (VK_ERROR_VALIDATION_FAILED_EXT == record_obj.result) return;\n'

                    post_call_record += f'if ({command.params[-1].name}) {{\n'
                    countIsPointer = '*' if command.params[-2].type == 'uint32_t' and command.params[-2].pointer else ''
                    post_call_record += f'for (uint32_t index = 0; index < {countIsPointer}{command.params[-1].length}; index++) {{\n'

                if command.name in partial_success_commands:
                    if command.name == 'vkCreateShadersEXT':
                        post_call_record += 'if (!pShaders[index]) continue;\n'
                    else:
                        post_call_record += 'if (!pPipelines[index]) continue;\n'

                allocator = command.params[-2].name if command.params[-2].type == 'VkAllocationCallbacks' else 'nullptr'
                objectDest = f'{command.params[-1].name}[index]' if objectArray else f'*{command.params[-1].name}'
                location = f'record_obj.location.dot(Field::{command.params[-1].name}, index)' if objectArray else 'record_obj.location'
                parent = command.params[0].name
                post_call_record += f'tracker.CreateObject({objectDest}, kVulkanObjectType{handle_type[2:]}, {allocator}, {location}, {parent});\n'
                if objectArray:
                    post_call_record += '}\n'
                    post_call_record += '}\n'
            # Physical device groups are not handles, but a set of handles, they need to be tracked as well
            elif handle_type == 'VkPhysicalDeviceGroupProperties':
                post_call_record += f'''
                    if ({command.params[-1].name}) {{
                        const RecordObject record_obj(vvl::Func::vkEnumeratePhysicalDevices, VK_SUCCESS);
                        for (uint32_t device_group_index = 0; device_group_index < *{command.params[-2].name}; device_group_index++) {{
                            PostCallRecordEnumeratePhysicalDevices({command.params[0].name}, &{command.params[-1].name}[device_group_index].physicalDeviceCount, {command.params[-1].name}[device_group_index].physicalDevices, record_obj);
                        }}
                    }}\n'''
        # Handle object destroy operations
        if isDestroy:
            # Check for special case where multiple handles are returned
            handle_param = command.params[-1] if 'ReleasePerformanceConfigurationINTEL' in command.name else command.params[-2]
            allocator = 'nullptr' if 'ReleasePerformanceConfigurationINTEL' in command.name else 'pAllocator'

            compatallocVUID = self.getAllocVUID(handle_param, "compatalloc")
            nullallocVUID = self.getAllocVUID(handle_param, "nullalloc")
            if handle_param.type in self.vk.handles:
                # Call Destroy a single time
                pre_call_validate += f'skip |= ValidateDestroyObject({handle_param.name}, kVulkanObjectType{handle_param.type[2:]}, {allocator}, {compatallocVUID}, {nullallocVUID}, error_obj.location);\n'
                pre_call_record += f'RecordDestroyObject({handle_param.name}, kVulkanObjectType{handle_param.type[2:]}, record_obj.location);\n'

        return pre_call_validate, pre_call_record, post_call_record

